﻿using System;
using System.Windows.Forms;
using System.Threading;
using System.Drawing;
using LuaVM.Helpers;
using LuaVM.Core;
using MFGLib;
using System.IO;
using LuaVM.Controls;
using System.Text.RegularExpressions;

namespace LuaVM
{
	public partial class MainForm : Form
	{
		LuaEnv m_lua = null;
		ConfigManager m_config = new ConfigManager();
		PluginManager m_plugins = new PluginManager();
		HotkeyManager m_hotkey = new HotkeyManager();
		FormThread m_thread = new FormThread();		
		SoundManager m_sound = new SoundManager();

		bool m_canClose = false;
		Icon m_appNormal = Icon.FromHandle(Properties.Resources.AppNormal.GetHicon());
		Icon m_appRunning = Icon.FromHandle(Properties.Resources.AppRunning.GetHicon());
		string m_scriptText = null;

		static readonly string AppFolder = Application.StartupPath;
		static readonly string PluginsFolder = AppFolder + "\\Plugins";
		static readonly string UserFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + '\\' + Application.ProductName;
		public MainForm()
		{			
			InitializeComponent();

			try
			{
				Directory.CreateDirectory(UserFolder);
			}
			catch
			{
			}

			splitContainer2.Panel1.Controls.Add(luaEditBox1);
			luaEditBox1.DocumentChanged = OnDocumentChanged;

			m_plugins.SelectionChange = OnPluginSelectionChanged;

			sideNavPanel1.MRUSelect = OnMRUSelected;
			sideNavPanel1.CSSelect = OnCSSelected;

			m_thread.OnStart += OnThreadStart;
			m_thread.OnStop += OnThreadStop;
			m_thread.OnProcess += ThreadProc;			
		}

		private void MainForm_Load(object sender, EventArgs e)
		{
			this.AllowDrop = true;
			trayIconMain.Visible = false;
			m_config.Load(UserFolder);

			UpdateFormStates();			

			sideNavPanel1.InitializeMRU(m_config.MRU);
			sideNavPanel1.ExpandAll();

			m_plugins.SetSelectedDllNames(m_config.Plugins);
			m_plugins.InitializePluginMenu(menuPlugins);
			m_plugins.InitializeManualMenu(menuHelpPluginManuals);			

			m_hotkey.Form = this;
			m_hotkey.Hotkey = m_config.Hotkey;
			m_hotkey.HotkeyStroke = OnGlobalHotkey;			
			m_hotkey.RegisterHotkey();
			
			luaEditBox1.Font = m_config.Font;
			if (m_config.FileName == null)
			{
				luaEditBox1.LoadTemp();				
			}
			else
			{
				luaEditBox1.LoadFile(m_config.FileName);
			}

			luaEditBox1.Modified = m_config.Modified;

			UpdateTitle();
			UpdateDocumentControls();
			UpdateThreadControls();			
		}

		private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
		{
			if (m_config.CloseToTray && !m_canClose)
			{
				Visible = false;
				trayIconMain.Visible = true;
				WindowState = FormWindowState.Minimized;
				e.Cancel = true;
				return;
			}

			if (!luaEditBox1.PromptForSave(true))
			{
				e.Cancel = true;
				return;
			}

			m_thread.Stop();
			m_hotkey.UnregisterHotkey();
			m_config.MRU = sideNavPanel1.GetMRU();
			m_config.Plugins = m_plugins.GetSelectedDllNames();
			m_config.Maximized = WindowState == FormWindowState.Maximized;
			
			m_config.FileName = luaEditBox1.FileName;
			m_config.Modified = luaEditBox1.Modified;
			if (luaEditBox1.FileName == null)
			{
				luaEditBox1.SaveTemp();
			}

			m_config.Save(UserFolder);
		}

		string m_lastCSFile = null;
		private void OnDocumentChanged(bool modified, string fileName)
		{
			UpdateTitle();
			UpdateDocumentControls();

			if (fileName == null)
			{
				return;
			}

			if (fileName != null && fileName != m_lastCSFile)
			{
				sideNavPanel1.AddMRU(fileName);
			}			
		}

		private void OnMRUSelected(string filePath)
		{
			luaEditBox1.LoadFile(filePath);
		}

		private void OnCSSelected(string filePath)
		{
			m_lastCSFile = filePath;
			luaEditBox1.LoadFile(filePath);
			luaEditBox1.FileName = null;
			UpdateTitle();
		}

		private void UpdateTitle()
		{
			string value = "LuaVM - " + (luaEditBox1.FileName ?? "<新建Lua文档>");
			if (luaEditBox1.Modified)
			{
				value += "*";
			}

			Text = value;
		}

		private void UpdateDocumentControls()
		{
			menuFileSave.Enabled = luaEditBox1.Modified;
			toolSave.Enabled = luaEditBox1.Modified;
			menuEditUndo.Enabled = luaEditBox1.CanUndo;
			toolUndo.Enabled = luaEditBox1.CanUndo;
			menuEditRedo.Enabled = luaEditBox1.CanRedo;
			toolRedo.Enabled = luaEditBox1.CanRedo;			
		}

		private void UpdateThreadControls()
		{
			bool alive = m_thread.IsAlive;

			toolRun.Enabled = !alive;
			toolStop.Enabled = alive;

			menuActionRun.Enabled = !alive;
			menuActionStop.Enabled = alive;

			menuPopupRun.Enabled = !alive;
			menuPopupStop.Enabled = alive;

			menuFile.Enabled = !alive;
			menuPlugins.Enabled = !alive;
			toolNew.Enabled = !alive;
			toolOpen.Enabled = !alive;

			trayIconMain.Icon = alive ? m_appRunning : m_appNormal;
			trayIconMain.Text = alive ? "LuaVM - 脚本运行中" : "LuaVM";

			this.Icon = alive ? m_appRunning : m_appNormal;
		}

		private void UpdateFormStates()
		{
			if (m_config.Width > 0 && m_config.Height > 0)
			{
				Width = m_config.Width;
				Height = m_config.Height;
			}

			if (m_config.Maximized)
			{
				WindowState = FormWindowState.Maximized;
			}

			if (m_config.Split1Distance > 0)
			{
				splitContainer1.SplitterDistance = m_config.Split1Distance;
			}

			if (m_config.Split2Distance > 0)
			{
				splitContainer2.SplitterDistance = m_config.Split2Distance;
			}

			splitContainer1.SplitterMoved += splitContainer1_SplitterMoved;
			splitContainer2.SplitterMoved += splitContainer2_SplitterMoved;

			menuOptionsCloseToTray.Checked = m_config.CloseToTray;
			scrollLogBox1.Font = m_config.Font;
			toolHotkey.Text = m_config.Hotkey.ToString();
		}

		private void OnGlobalHotkey()
		{
			if (m_thread.IsAlive)
			{
				StopThread();
			}
			else
			{
				StartThread();
			}
		}

		private void OnPluginSelectionChanged(LuaDllPlugin[] selectedPlugins)
		{
			// 根据已加载插件列表更新编辑器语法高亮			
			luaEditBox1.Syntax.UpdatePlugins(selectedPlugins);
			luaEditBox1.ReloadHighlighting();
		}

		#region 线程操作
		private void StartThread()
		{
			if (m_thread.IsAlive)
			{
				return;
			}

			m_lua = new LuaEnv();			
			m_lua.LuaPrint = LuaPrint; // 实现lua环境中的print(...)结果输出到本窗体RichTextBox控件中
			m_lua["aborting"] = false;
			m_sound.Create(m_lua);
			m_plugins.Attach(m_lua);

			if (luaEditBox1.FileName != null)
			{
				m_lua.SetPackagePath(Path.GetDirectoryName(luaEditBox1.FileName), true);
			}

			m_scriptText = luaEditBox1.Text;
			m_thread.Start();
		}

		private void StopThread()
		{
			if (!m_thread.IsAlive)
			{
				return;
			}			

			m_lua["aborting"] = true;
			m_thread.Stop();
		}

		private void OnThreadStart()
		{
			m_sound.PlayStart();
			UpdateThreadControls();
			scrollLogBox1.Clear();
			scrollLogBox1.Append("脚本线程已启动。", LogTextType.Highlight);
		}

		private void OnThreadStop()
		{
			m_sound.StopAlert();
			m_sound.PlayStop();

			try
			{
				m_plugins.Detach();
			}
			catch
			{
			}

			UpdateThreadControls();
			scrollLogBox1.Append("脚本线程已终止。", LogTextType.Highlight);
			scrollLogBox1.Append("");
		}

		private void ThreadProc()
		{
			try
			{				
				m_lua.DoString(m_scriptText);
				m_lua.Execute();
			}
			catch (ThreadAbortException)
			{
				scrollLogBox1.Append("用户终止脚本运行。", LogTextType.Error);
			}
			catch (LuaInterface.Exceptions.LuaScriptException ex)
			{
				string message;
				if (ex.Message == "A .NET exception occured in user-code" && ex.InnerException.Message != null)
				{
					message = ex.InnerException.Message;
				}
				else
				{
					message = ex.Message;
				}

				Match m = Regex.Match(message, @"\[string ""(.+)""\]:");
				if (m.Success && m.Groups.Count == 2)
				{
					message = m.Groups[1].Value + message.Substring(m.Length);
				}

				scrollLogBox1.Append(message, LogTextType.Error);
			}
			catch (EntryPointNotFoundException)
			{
				scrollLogBox1.Append("当前Lua环境中未找到main()函数。", LogTextType.Verbose);
			}
			catch (Exception ex)
			{
				scrollLogBox1.Append(ex.Message, LogTextType.Error);
			}			
		}

		private void LuaPrint(string[] items)
		{
			scrollLogBox1.Append(string.Join(", ", items));
		}
		#endregion		

		private void menuFileExis_Click(object sender, EventArgs e)
		{
			m_canClose = true;
			Close();
		}

		private void toolHotkey_Click(object sender, EventArgs e)
		{
			if (m_hotkey.DoConfig())
			{
				toolHotkey.Text = m_hotkey.Hotkey.ToString();
			}
		}

		private void toolRun_Click(object sender, EventArgs e)
		{
			StartThread();
		}

		private void toolStop_Click(object sender, EventArgs e)
		{
			StopThread();
		}

		private void menuFileOpen_Click(object sender, EventArgs e)
		{
			luaEditBox1.LoadFile();
		}

		private void toolOpen_Click(object sender, EventArgs e)
		{
			luaEditBox1.LoadFile();
		}

		private void menuFileNew_Click(object sender, EventArgs e)
		{
			luaEditBox1.NewFile();
		}

		private void menuFileSave_Click(object sender, EventArgs e)
		{
			luaEditBox1.SaveFile();
		}

		private void menuFileSaveAs_Click(object sender, EventArgs e)
		{
			luaEditBox1.SaveFileAs();
		}

		private void toolSave_Click(object sender, EventArgs e)
		{
			luaEditBox1.SaveFile();
		}

		private void menuEditUndo_Click(object sender, EventArgs e)
		{
			luaEditBox1.Undo();
		}

		private void menuEditRedo_Click(object sender, EventArgs e)
		{
			luaEditBox1.Redo();
		}

		private void menuEditCut_Click(object sender, EventArgs e)
		{
			luaEditBox1.Cut();
		}

		private void menuEditCopy_Click(object sender, EventArgs e)
		{
			luaEditBox1.Copy();
		}

		private void menuEditPaste_Click(object sender, EventArgs e)
		{
			luaEditBox1.Paste();
		}

		private void menuEditSelectAll_Click(object sender, EventArgs e)
		{
			luaEditBox1.SelectAll();
		}

		private void menuActionRun_Click(object sender, EventArgs e)
		{
			StartThread();
		}

		private void menuActionStop_Click(object sender, EventArgs e)
		{
			StopThread();
		}

		private void toolUndo_Click(object sender, EventArgs e)
		{
			luaEditBox1.Undo();
		}

		private void toolRedo_Click(object sender, EventArgs e)
		{
			luaEditBox1.Redo();
		}		

		private void menuOptionsFont_Click(object sender, EventArgs e)
		{
			FontDialog dlg = new FontDialog();
			//MethodInfo mi = typeof(FontDialog).GetMethod("SetOption", BindingFlags.NonPublic | BindingFlags.Instance);
			//mi.Invoke(dlg, new object[] { 0x40000, false });

			dlg.Font = luaEditBox1.Font;
			if (dlg.ShowDialog(this) == DialogResult.OK)
			{
				luaEditBox1.Font = dlg.Font;
				scrollLogBox1.Font = dlg.Font;
			}
		}

		private void menuHelpLua51_Click(object sender, EventArgs e)
		{
			System.Diagnostics.Process.Start("http://www.lua.org/manual/5.1");
		}		

		private void menuHelpLuaNMHelp_Click(object sender, EventArgs e)
		{
			System.Diagnostics.Process.Start(Application.StartupPath + "\\LuaVM.chm");
		}

		private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
		{
			AboutBox1 dlg = new AboutBox1();
			dlg.ShowDialog(this);
		}		

		private void menuPopupShowUI_Click(object sender, EventArgs e)
		{
			Visible = true;
			Win32API.Window.ShowWindow(Handle, Win32API.Window.SW_RESTORE);
			Win32API.Window.SetForegroundWindow(Handle);
			trayIconMain.Visible = false;
		}

		private void menuPopupCopy_Click(object sender, EventArgs e)
		{
			Clipboard.SetDataObject(scrollLogBox1.SelectedText);
		}

		private void menuPopupClear_Click(object sender, EventArgs e)
		{
			scrollLogBox1.Clear();
		}

		private void menuOptionsCloseToTray_Click(object sender, EventArgs e)
		{
			m_config.CloseToTray = !m_config.CloseToTray;
			menuOptionsCloseToTray.Checked = m_config.CloseToTray;
		}

		private void MainForm_ResizeEnd(object sender, EventArgs e)
		{
			m_config.Width = Width;
			m_config.Height = Height;
		}

		private void splitContainer1_SplitterMoved(object sender, SplitterEventArgs e)
		{
			m_config.Split1Distance = e.SplitX;
		}

		private void splitContainer2_SplitterMoved(object sender, SplitterEventArgs e)
		{
			m_config.Split2Distance = e.SplitY;
		}
		
		private void toolNew_Click(object sender, EventArgs e)
		{
			luaEditBox1.NewFile();
		}

		private void menuEdit_DropDownOpened(object sender, EventArgs e)
		{
			menuEditUndo.Enabled = luaEditBox1.CanUndo;
			menuEditRedo.Enabled = luaEditBox1.CanRedo;
			menuEditCut.Enabled = luaEditBox1.HasSomethingSelected;
			menuEditCopy.Enabled = luaEditBox1.HasSomethingSelected;
			menuEditPaste.Enabled = luaEditBox1.CanPaste;
			menuEditDelete.Enabled = luaEditBox1.HasSomethingSelected;
		}

		private void menuEditDelete_Click(object sender, EventArgs e)
		{
			luaEditBox1.Delete();
		}
		
		private static string GetDragDropLua(DragEventArgs e)
		{
			if (!e.Data.GetDataPresent(DataFormats.FileDrop))
			{
				return null;
			}

			Array array = e.Data.GetData(DataFormats.FileDrop) as Array;
			if (array == null || array.Length < 1)
			{
				return null;
			}

			string fileName = array.GetValue(0).ToString();
			if (string.IsNullOrEmpty(fileName) || !fileName.ToLower().EndsWith(".lua"))
			{
				return null;
			}

			return fileName;
		}

		private void MainForm_DragEnter(object sender, DragEventArgs e)
		{
			string fileName = GetDragDropLua(e);
			if (fileName == null)
			{
				return;
			}

			e.Effect = DragDropEffects.Link;
		}

		private void MainForm_DragDrop(object sender, DragEventArgs e)
		{
			string fileName = GetDragDropLua(e);
			if (fileName == null)
			{
				return;
			}

			luaEditBox1.LoadFile(fileName);
		}		
	}
}
